package com.jugheadzhou.auth.core.websocket;

import com.jugheadzhou.auth.core.websocket.event.TodoAtRemoved;
import com.jugheadzhou.auth.core.websocket.utils.WebSocketUtil;
import org.apache.commons.lang3.StringUtils;

import javax.websocket.Session;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;

/**
 * 管理websocket的session,可以使用Map，可以自己添加接口功能实现，并放到容器中
 * 注意：放在容器中的名字必须是 WebSocketManager.WEBSOCKET_MANAGER_NAME 即：webSocketManager
 *
 * @author zhaorong
 */
public interface WebSocketManager {

    /**
     * 根据标识获取websocket session
     *
     * @param connectionId 标识
     * @return WebSocket
     */
    WebSocketConnection get(String connectionId);

    /**
     * 放入一个 websocket session
     *
     * @param connectionId        标识
     * @param webSocketConnection websocket
     */
    void put(String connectionId, WebSocketConnection webSocketConnection);

    /**
     * 删除
     *
     * @param connectionId 标识
     */
    void remove(String connectionId);

    /**
     * 获取当前机器上的保存的WebSocket
     *
     * @return WebSocket Map
     */
    Map<String, WebSocketConnection> localWebSocketMap();

    /**
     * 统计所有在线人数
     *
     * @return 所有在线人数
     */
    default int size() {
        return localWebSocketMap().size();
    }

    /**
     * 发送消息
     *
     * @param connectionId 标识
     * @param message      消息
     */
    void sendMessage(String connectionId, String message);

    /**
     * 发送消息
     *
     * @param connectionId 标识
     * @param message      消息
     */
    void sendMessageAsync(String connectionId, String message);

    /**
     * 广播
     *
     * @param message 消息
     */
    void broadcast(String message);

    /**
     * WebSocket接收到消息的函数调用
     *
     * @param connectionId 标识
     * @param message      消息内容
     */
    void onMessage(String connectionId, String message);

    /**
     * 建立连接
     *
     * @param connectionId
     * @param session
     */
    default void connect(String connectionId, Session session) {
        if (StringUtils.isNotBlank(connectionId)) {
            WebSocketConnection webSocketConnection = new WebSocketConnection();
            webSocketConnection.setConnectionId(connectionId);
            webSocketConnection.setSession(session);
            webSocketConnection.setLastHeart(LocalDateTime.now());
            //后面的覆盖前面的
            put(connectionId, webSocketConnection);
        }
    }

    /**
     * 断开连接
     *
     * @param connectionId
     */
    default void disconnect(String connectionId) {
        if (StringUtils.isNotBlank(connectionId)) {
            remove(connectionId);
        }
    }

    /**
     * 心跳检测发起
     *
     * @param connectionId
     */
    default void ping(String connectionId) {
        if (StringUtils.isNotBlank(connectionId)) {
            System.out.println("心跳检测发起 connectionId= " + connectionId);
            WebSocketConnection webSocketConnection = get(connectionId);
            WebSocketUtil.sendMessageAsync(webSocketConnection.getSession(), "ping");
        }
    }

    /**
     * 更新心跳
     * 判断是否心跳响应内容
     *
     * @param connectionId
     * @param message
     */
    default boolean isPong(String connectionId, String message) {
        WebSocketConnection webSocketConnection = get(connectionId);
        if (webSocketConnection != null) {
            System.out.println("update heartbeat time for connectionId=" + connectionId + ", message=" + message);
            webSocketConnection.setLastHeart(LocalDateTime.now());
        }
        return "pong".equals(message);
    }

    /**
     * 在websocket过期时间内发送心跳检测消息
     * 定时检测websocket的心跳时间跟现在的间隔，超过设定的值说明失去了心跳
     *
     * @param expired       心跳检测过期间隔时间
     * @param todoAtRemoved 在删除的时候额外需要做的事情
     */
    default void check(long expired, TodoAtRemoved todoAtRemoved) {
        Map<String, WebSocketConnection> socketMap = localWebSocketMap();
        LocalDateTime now = LocalDateTime.now();
        socketMap.entrySet().parallelStream().forEach(entry -> {
            String connectionId = entry.getKey();
            try {
                WebSocketConnection webSocketConnection = entry.getValue();
                long interval = Duration.between(webSocketConnection.getLastHeart(), now).toMillis();
                if (interval < expired) {
                    // 在websocket过期时间内发送心跳检测消息
                    ping(connectionId);
                } else {
                    // 发送心跳失效通知
                    this.sendMessage(connectionId, "websocket连接已失效，请重新连接。");
                    // 删除连接
                    this.remove(connectionId);
                    // 删除连接额外操作
                    todoAtRemoved.todoWith(webSocketConnection);
                }
            } catch (Exception e) {
                System.out.println("心跳检测发生异常，当前连接ID = " + connectionId);
                e.printStackTrace();
            }
        });
    }
}
